// package GIN框架中的业务逻辑层
package controller

import (
	"encoding/json"
	"net/http"
	"osiris/cli"
	"osiris/config"
	"osiris/core/state"
	"osiris/core/txpool"
	"osiris/dto"
	"osiris/logger"
	"osiris/ocrypto/ecdsa"
	"osiris/p2p"
	"strconv"

	"github.com/gin-gonic/gin"
)

// AccountController 账户相关的业务逻辑
type AccountController struct{}

// RegisterHandler 账户注册（返回AccountDto：私钥、地址、初始随机数、最大随机数）
//
//	@receiver controller
//	@param context
func (controller AccountController) RegisterHandler(context *gin.Context) {
	var accountDto dto.AccountStateDto
	var registerTxData dto.TxData

	for {
		//生成账户私钥、地址
		privKey, addrStr, nonce, err := ecdsa.GenerateAccount()
		if err != nil {
			context.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		registerTxData = dto.TxData{
			TxType:   dto.TX_REGISTER,
			FromAddr: addrStr,
			ToAddr:   addrStr,
			Nonce:    nonce,
			MaxNonce: config.MaxAccountNonce.Int64(),
		}

		//打时间戳、用账户的私钥对交易签名
		registerTxData.HashAndSign(privKey, true)

		//将账户创建交易加到交易池
		succeed := txpool.AddLocalTx(&registerTxData)
		if !succeed {
			logger.Warn(map[string]interface{}{"[controller] [RegisterHandler] txpool.AddLocalTx": "tx denied by txpool"})
			continue
		}

		cli.AddConfirmingClient(addrStr, privKey)

		//成功将创建的账户写入状态树后退出循环
		accountDto = dto.AccountStateDto{
			PrivKeyStr: ecdsa.ECDSAPrivKeyToStr(privKey),
			AcountAddr: addrStr,
			CoinSince:  registerTxData.CoinSince,
			Nonce:      nonce,
			MaxNonce:   config.MaxAccountNonce.Int64(),
		}

		break
	}

	//广播交易
	go func(tempTxData dto.TxData) {
		txDto := p2p.GenerateTxDto(tempTxData)
		jsonStr, err := json.Marshal(txDto)
		if err != nil {
			logger.Error(map[string]interface{}{"[controller] [RegisterHandler] marshal txDto": err})
		} else {
			p2p.OpenStream(p2p.TxProtocol{}, "", string(jsonStr), nil)
		}

	}(registerTxData)

	//返回AccountDto
	bytes, err := json.Marshal(accountDto)
	if err != nil {
		context.JSON(http.StatusInternalServerError, gin.H{
			"accountDto serialization": err.Error(),
		})
		return
	}

	context.JSON(http.StatusOK, string(bytes))
}

// GetHandler 通过账户地址查询账户状态信息
//
//	@receiver controller
//	@param context
func (controller AccountController) GetHandler(context *gin.Context) {
	chainIDStr := context.Param("chainID")
	chainID, err := strconv.Atoi(chainIDStr)
	if err != nil {
		context.JSON(http.StatusBadRequest, gin.H{
			"chainID Atoi": err.Error(),
		})
		return
	}

	addrStr := context.Param("addr")
	leaf, exist := state.GetAccountLeaf(ecdsa.StrToAccountBytes(addrStr), chainID)

	accountDto := dto.AccountStateDto{
		BaseDto: dto.BaseDto{
			Code: dto.CODE_ACCOUNT_NOT_EXIST,
		},
		AcountAddr: addrStr,
	}
	if exist {
		accountDto.Code = dto.CODE_ACCOUNT_EXIST
		accountDto.Asset = leaf.Asset
		accountDto.CoinSince = leaf.CoinSince
		accountDto.Nonce = leaf.Nonce
		accountDto.MaxNonce = leaf.MaxNonce
	}

	accountDtoBytes, err2 := json.Marshal(accountDto)
	if err2 != nil {
		context.JSON(http.StatusInternalServerError, gin.H{
			"accountDto Serialization": err2.Error(),
		})
		return
	}
	context.JSON(http.StatusOK, string(accountDtoBytes))
}

// TxHandler 处理客户端提出的交易（包括代币重置、转账）
//
//	@receiver controller
//	@param context
func (controller AccountController) TxHandler(context *gin.Context) {
	var txData dto.TxData
	if err1 := context.BindJSON(&txData); err1 != nil {
		context.JSON(http.StatusBadRequest, gin.H{"error": err1.Error()})
		return
	}

	//检查txData的哈希和签名
	if !txData.Verify() {
		context.JSON(http.StatusBadRequest, gin.H{"error": "txData crypto-verification fail!"})
		return
	}

	//向本地交易池中添加交易
	succeed := txpool.AddLocalTx(&txData)
	if !succeed {
		context.JSON(http.StatusBadRequest, gin.H{"error": "txData denied by txpool!"})
		return
	}

	//广播交易
	go func(tempTxData dto.TxData) {

		txDto := p2p.GenerateTxDto(tempTxData)
		jsonStr, err := json.Marshal(txDto)
		if err != nil {
			logger.Error(map[string]interface{}{"[controller] [TxHandler] marshal txDto": err})
		} else {
			p2p.OpenStream(p2p.TxProtocol{}, "", string(jsonStr), nil)
		}

	}(txData)

	context.JSON(http.StatusOK, "successfully submit txData")
}
